/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2008 Apple Inc.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */

//
// QTFile:
//   The central point of control for a file in the QuickTime File Format.


// -------------------------------------
// Includes
//
#include <stdio.h>
#include <stdlib.h>
#include "SafeStdLib.h"
#include <string.h>

#include "OSMutex.h"
#include "OSMemory.h"

#include "QTFile.h"

#include "QTAtom.h"
#include "QTAtom_mvhd.h"
#include "QTAtom_tkhd.h"

#include "QTTrack.h"
#include "QTHintTrack.h"

#include <fcntl.h>



// -------------------------------------
// Macros
//
//#define DEBUG_PRINT(s) if(fDebug) qtss_printf s
//#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s


// -------------------------------------
// Class state cookie
//

QTFile_FileControlBlock::QTFile_FileControlBlock(void)
    : fDataFD(NULL), fDataBufferPool(NULL),
      fDataBufferSize(0), fDataBufferPosStart(0), fDataBufferPosEnd(0),
      fCurrentDataBuffer(NULL), fPreviousDataBuffer(NULL),
      fCurrentDataBufferLength(0), fPreviousDataBufferLength(0),
      fNumBlocksPerBuff(1),fNumBuffs(1),
      fCacheEnabled(false)
      
{
}

QTFile_FileControlBlock::~QTFile_FileControlBlock(void)
{
    if( fDataBufferPool != NULL )
        delete[] fDataBufferPool;
#if DSS_USE_API_CALLBACKS
    (void)QTSS_CloseFileObject(fDataFD);
#endif

}


void QTFile_FileControlBlock::Set( char * DataPath)
{
#if DSS_USE_API_CALLBACKS
    (void)QTSS_OpenFileObject(DataPath, qtssOpenFileReadAhead, &fDataFD);
#else
    fDataFD.Set(DataPath);
#endif

}

Bool16 QTFile_FileControlBlock::ReadInternal(FILE_SOURCE *dataFD, UInt64 inPosition, void* inBuffer, UInt32 inLength, UInt32 *inReadLenPtr)
{
        UInt32 readLen = 0;
        if (NULL != inReadLenPtr)
            *inReadLenPtr = 0;
            
#if DSS_USE_API_CALLBACKS
        QTSS_Error  theErr = QTSS_Seek(*dataFD, inPosition);
        if (theErr == QTSS_NoErr)
            theErr = QTSS_Read(*dataFD, inBuffer, inLength, &readLen);
        if (theErr != QTSS_NoErr)
            return false;
#else
        if( dataFD->Read(inPosition, inBuffer, inLength, &readLen) != OS_NoErr )
            return false;
#endif
        if (NULL != inReadLenPtr)
            *inReadLenPtr = readLen;
        
        if(inReadLenPtr == NULL && readLen != inLength) //external reads expect false if it fails to read all the requested data.
            return false;
            
        return true;
}


Bool16 QTFile_FileControlBlock::Read(FILE_SOURCE *dflt, UInt64 inPosition, void* inBuffer, UInt32 inLength)
{
    // Temporary vars
    UInt32 rcSize;

    // General vars
    FILE_SOURCE     *dataFD;

    // success or failure
    Bool16 result = false;

    // Get the file descriptor.  If the FCB is NULL, or the descriptor in
    // the FCB is -1, then we need to use the class' descriptor.
    if (this->IsValid())
        dataFD = &fDataFD;
    else
        dataFD = dflt;

    if  (
            ( !fCacheEnabled) ||    // file control block caching disabled
            ( inLength > fDataBufferSize ) ||   // too big for this cache
            ( inPosition < fDataBufferPosStart) // backing up
        )
    {
        //if ( !fCacheEnabled) qtss_printf("QTFile_FileControlBlock::Read  cache not enabled\n");
        //if ( inLength > fDataBufferSize) qtss_printf("QTFile_FileControlBlock::Read  read too big for cache len=%"   _U32BITARG_   " max%"   _U32BITARG_   "\n",inLength,fDataBufferSize);
        //if ( inPosition < fDataBufferPosStart) qtss_printf("QTFile_FileControlBlock::Read  backing up skipping cache missed by =%"   _U32BITARG_   " bytes\n", fDataBufferPosStart - inPosition);
        result = this->ReadInternal(dataFD, inPosition, inBuffer, inLength);
        goto done;
    }
    

    // Is the requested block of data in our data buffer?  If not, read in the
    // section of the file where this piece of data is.
    if( (inPosition < fDataBufferPosStart) || ((inPosition + inLength) > fDataBufferPosEnd) ) 
    {
        // If this is a forward-moving, contiguous read, then we can keep the
        // current buffer around.
        
        if (    (fCurrentDataBufferLength != 0)
            &&  ((fDataBufferPosEnd - fCurrentDataBufferLength) <= inPosition)
            && ((fDataBufferPosEnd + fDataBufferSize) >= (inPosition + inLength))
           ) 
        {
            // Temporary vars
            char        *TempDataBuffer;
                
            //qtss_printf("QTFile_FileControlBlock::Read forward read inPosition=%" _64BITARG_ "u fPreviousDataBuffer=%" _64BITARG_ "u start=%" _64BITARG_ "u\n",inPosition,fDataBufferPosStart,fDataBufferPosEnd);
                
            // First, demote the current buffer.
            fDataBufferPosStart += fPreviousDataBufferLength;
            TempDataBuffer = fPreviousDataBuffer;
                
            fPreviousDataBuffer = fCurrentDataBuffer;
            fPreviousDataBufferLength = fCurrentDataBufferLength;
                
            fCurrentDataBuffer = TempDataBuffer;
            fCurrentDataBufferLength = 0;
                
            //
            // Then, fill the now-current buffer with data.
            if (!this->ReadInternal(dataFD, fDataBufferPosEnd, fCurrentDataBuffer, fDataBufferSize, &rcSize) )
                goto done;
        
            //Assert(rcSize == fDataBufferSize);
            fCurrentDataBufferLength = (UInt32)rcSize;
            fDataBufferPosEnd += fCurrentDataBufferLength;
        } 
        else 
        {   
            //qtss_printf("QTFile_FileControlBlock::Read not a contiguous forward read inPosition=%" _64BITARG_ "u fPreviousDataBuffer=%" _64BITARG_ "u missed=%" _64BITARG_ "d\n ",inPosition,fDataBufferPosStart,(fDataBufferPosStart > inPosition) ? fDataBufferPosStart-inPosition: inPosition - fDataBufferPosStart);
            
            // We need to play with our current and previous data buffers in
            // order to skip around while reading.
            fCurrentDataBuffer = fDataBufferPool;
            fCurrentDataBufferLength = 0;

            fPreviousDataBuffer = (char *)fDataBufferPool + fDataBufferSize;
            fPreviousDataBufferLength = 0;

            fDataBufferPosStart = inPosition;
            
            if (! this->ReadInternal(dataFD, fDataBufferPosStart, fCurrentDataBuffer, fDataBufferSize, &rcSize) )
                goto done;

            //Assert(rcSize == fDataBufferSize);
            fCurrentDataBufferLength = (UInt32)rcSize;
            fDataBufferPosEnd = fDataBufferPosStart + fCurrentDataBufferLength;
        }

    }
    
    //
    // Copy the data out of our buffer(s).
    {
        // General vars
        UInt64      ReadLength = inLength;
        UInt64      ReadOffset = inPosition - fDataBufferPosStart;
        
        //
        // Figure out if doing a continuous copy would cause us to cross a
        // buffer boundary.
        if  (   (inPosition < (fDataBufferPosStart + fPreviousDataBufferLength))
            &&  ((ReadOffset + ReadLength) > fPreviousDataBufferLength ) 
            ) 
        {
            // Temporary vars
            char    *pBuffer = (char *)inBuffer;
                                
            //
            // Read the first part of the block.
            ReadLength = fDataBufferSize - ReadOffset;
            if (ReadLength <= (fPreviousDataBufferLength - ReadOffset) )
                ::memcpy(pBuffer, fPreviousDataBuffer + ReadOffset, (UInt32) ReadLength);
            else
                goto done;
                
            pBuffer += ReadLength;
                
            //
            // Read the last part of the block.
            ReadLength = inLength - ReadLength;
            if (ReadLength <= fCurrentDataBufferLength )
            {   ::memcpy(pBuffer, fCurrentDataBuffer, (UInt32) ReadLength);
                result = true;
            }
            //
            // Or maybe this is a continuous copy out of the old buffer.
        } 
        else if ( inPosition < (fDataBufferPosEnd - fCurrentDataBufferLength) ) 
        {
            if (ReadLength <= (fPreviousDataBufferLength - ReadOffset) )
            {   ::memcpy(inBuffer, fPreviousDataBuffer + ReadOffset, (UInt32)ReadLength);

                result = true; 
            }               
        } 
        else 
        {
            ReadOffset -= fPreviousDataBufferLength;
            if (ReadLength <= (fCurrentDataBufferLength - ReadOffset) )
            {   ::memcpy(inBuffer, fCurrentDataBuffer + ReadOffset, (UInt32) ReadLength);
                result = true;
            }               
            
        }
    }

done:
    // We're done.
    return result;
}


void QTFile_FileControlBlock::AdjustDataBufferBitRate(UInt32 inUnitSizeInK, UInt32 inFileBitRate, UInt32 inNumBuffSizeUnits, UInt32 inMaxBitRateBuffSizeInBlocks)
{
    if (!fCacheEnabled)
        return;
        
    // General vars
    UInt32  newDataBufferSizeInUnits = inNumBuffSizeUnits;
    UInt32  newDataBufferSize = 0;
    UInt32  newUnitSizeBytes = 0;
    
    if (inUnitSizeInK < 1)
        inUnitSizeInK = 32;
    
    newUnitSizeBytes = inUnitSizeInK * 1024;

    if (inMaxBitRateBuffSizeInBlocks < 1)
        inMaxBitRateBuffSizeInBlocks = kMaxDefaultBlocks;
        
    if (inFileBitRate == 0) // set the maximum
        inFileBitRate = inMaxBitRateBuffSizeInBlocks * newUnitSizeBytes;
        
    if (inNumBuffSizeUnits < 1) // calculate if not set to a given number
    {
        newDataBufferSizeInUnits = (inFileBitRate + newUnitSizeBytes) / newUnitSizeBytes; // 32k bytes of buffer for every 32k bits in rate
        if( newDataBufferSizeInUnits > inMaxBitRateBuffSizeInBlocks ) // Limit the buffer size value to a reasonable maximum. should be pref
            newDataBufferSizeInUnits = inMaxBitRateBuffSizeInBlocks;
    }
    
    newDataBufferSize = newDataBufferSizeInUnits * kBlockByteSize;
    
    //qtss_printf("QTFile_FileControlBlock::AdjustDataBuffer private buffers NewDataBufferSizeInUnits =%"   _U32BITARG_   " NewDataBufferSize = %"   _U32BITARG_   "\n",newDataBufferSizeInUnits,newDataBufferSize);

    // Free the old buffer.
    delete[] fDataBufferPool;
    fDataBufferSize     = newDataBufferSize;
    fDataBufferPool     = NEW char[2 * newDataBufferSize]; // 2 contiguous buffers
    fCurrentDataBuffer  = fDataBufferPool;
    fPreviousDataBuffer = (char *)fDataBufferPool + newDataBufferSize;
    
    fDataBufferPosStart = 0;
    fDataBufferPosEnd   = 0;
    fCurrentDataBufferLength = 0;
    fPreviousDataBuffer = 0;
    
}
